package com.yanqu.road.server.manger.log;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.yanqu.road.dao.db.*;
import com.yanqu.road.entity.log.*;
import com.yanqu.road.entity.log.maidian.BaseLogEntity;
import com.yanqu.road.logic.config.ChannelConfig;
import com.yanqu.road.logic.model.ck.CkDbData;
import com.yanqu.road.logic.model.ck.CkLogParam;
import com.yanqu.road.logic.model.ck.CollectLogParam;
import com.yanqu.road.server.GameServer;
import com.yanqu.road.utils.ConfigHelper;
import com.yanqu.road.utils.JsonUtils;
import com.yanqu.road.utils.date.DateHelper;
import com.yanqu.road.utils.executor.ScheduledThreadPoolHelper;
import com.yanqu.road.utils.http.HttpHelper;
import com.yanqu.road.utils.property.Property;
import com.yanqu.road.utils.property.PropertyHelper;
import com.yanqu.road.utils.string.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;

/**
 * @Description
 * @Author cwq
 * @Data 2022/4/29 17:04
 */
public class AutoLogMgr {
    private static Logger logger = LogManager.getLogger(AutoLogMgr.class);

    /**
     * 需要存储的日志 k:class v:存储的对象
     */
    private static HashMap<Class, List<Object>> saveLogListMap = new HashMap<>();

    /**
     * 需要保存的日志数量
     */
    private static volatile int countNum = 0;

    /**
     * 需要保存的clickhouse日志数量
     */
    private static volatile int ckCountNum = 0;

    /**
     * 数数平台埋点
     */
    private static GameServerLogTraceBuilder logTraceBuilder = new GameServerLogTraceBuilder();

    /**
     * 添加需要保存 日志对象
     *
     * @param obj
     */
    public static void add(Object obj) {
        try {
            if(GameServer.getInstance().getDbTableInfoMgr() == null){
                return;
            }
            TableInfo tableInfo = GameServer.getInstance().getDbTableInfoMgr().getTableInfoByClass(obj.getClass());
            if(tableInfo == null){
                logger.error("AutoLogMgr add error, DBTableInfoMgr tableInfoMap not found class : {}", obj.getClass());
                return;
            }
            boolean dataUpload = tableInfo.isDataUpload() && BaseLogEntity.class.isAssignableFrom(obj.getClass());
            if(dataUpload){
                BaseLogEntity baseLogEntity = (BaseLogEntity)obj;
                //注入工具
                baseLogEntity.setTraceBuilder(logTraceBuilder);
                //时间提前
                baseLogEntity.build("#time", DateHelper.getCurrentDateTimeString(System.currentTimeMillis()));
                //baseLogEntity.buildCommonData();
            }
            // 加点基础字段
            if (ServerBaseLog.class.isAssignableFrom(obj.getClass())) {
                builderBaseLog((ServerBaseLog) obj);
            }
            addLog(obj);
        }catch (Exception e){
            logger.error("AutoLogMgr add error",e);
        }
    }

    /**
     * 添加日志
     *
     * @param obj
     */
    public static void addLog(Object obj) {
        synchronized (saveLogListMap) {
            Class cls = obj.getClass();
            List<Object> list = saveLogListMap.get(cls);
            if (list == null) {
                list = new ArrayList<>();
                saveLogListMap.put(cls, list);
            }
            list.add(obj);
        }
    }

    private static BaseLog builderBaseLog(ServerBaseLog serverBaseLog) {
        serverBaseLog.setSid(GameServer.getInstance().getServerId());
        // 没有日志时间默认按当前时间
        if (serverBaseLog.getTime() == 0L) {
            serverBaseLog.setTime(System.currentTimeMillis());
        }
        return serverBaseLog;
    }

    /**
     * 保存 日志对象
     */
    public static synchronized void save() {
        HashMap<Class, List<Object>> tempSaveLogListMap = null;
        synchronized (saveLogListMap) {
            tempSaveLogListMap = new HashMap<>(saveLogListMap);
            saveLogListMap.clear();
        }
        for (Map.Entry<Class, List<Object>> entry : tempSaveLogListMap.entrySet()) {
            TableInfo tableInfo = GameServer.getInstance().getDbTableInfoMgr().getTableInfoByClass(entry.getKey());
            Class clz = entry.getKey();
            boolean dataUpload = tableInfo.isDataUpload() && BaseLogEntity.class.isAssignableFrom(clz);
            try {
                List<Map<Integer, DbParameter>> dbParameterList = new ArrayList<>();
                for (Object data : entry.getValue()) {
                    BaseLogEntity baseLogEntity = null;
                    if(dataUpload){
                        baseLogEntity = (BaseLogEntity)data;
                    }
                    Map<Integer, DbParameter> para = new HashMap<>();
                    int i = 1;
                    for (int j = 0; j < tableInfo.getFieldInfoList().size(); j++) {
                        DbFieldInfo dbFieldInfo = tableInfo.getFieldInfoList().get(j);
                        boolean isSaveJson = dbFieldInfo.isSaveJson();
                        Field field = dbFieldInfo.getField();
                        field.setAccessible(true);
                        Object obj = field.get(data);
                        if (isSaveJson) {
                            // 转json存储
                            obj = JSONArray.toJSONString(obj);
                        } else if(obj instanceof Property) {
                            obj = PropertyHelper.parsePropertyToString((Property) obj);
                        }
//                        if(dbFieldInfo.isInsert()) {
                            para.put(i++, new DbParameter(dbFieldInfo.getSqlType(), obj, false));
//                        }
                        if (baseLogEntity != null) {
                            String attributeName = dbFieldInfo.getDataAttribute();
                            if (!StringUtils.isNullOrEmpty(attributeName)) {
                                if (tableInfo.getCustomAttributeMap().containsKey(attributeName)) {
                                    baseLogEntity.customGet(tableInfo.getCustomAttributeMap().get(attributeName), attributeName);
                                } else {
                                    if (isSaveJson) {//传对象不传json字符串
                                        baseLogEntity.build(attributeName, field.get(data));
                                    } else {
                                        baseLogEntity.build(attributeName, obj);
                                    }
                                }
                            }

                        }
                    }
                    dbParameterList.add(para);
                    if(baseLogEntity != null){
                        baseLogEntity.buildCommonData();
                        baseLogEntity.trace(StringUtils.isNullOrEmpty(tableInfo.getEventName()) ? tableInfo.getTableName() : tableInfo.getEventName());
                    }
                }
                // 上传到clickhouse就不入库了
                if (isClickHouseOpen() && tableInfo.isUploadCk()) {
                    addCKLog(clz, tableInfo.getSaveSql(), dbParameterList);
                } else {
                    addMysqlLog(clz, tableInfo.getSaveSql(), dbParameterList);
                }
            } catch (Exception e) {
                logger.error(e);
            }
        }
    }

    /**
     * 判断服务是否正常
     */
    public static boolean isClickHouseOpen() {
        return ChannelConfig.CLICKHOUSE_LOG_SERVER_SWITCH == 1 && !StringUtils.isNullOrEmpty(ChannelConfig.CLICKHOUSE_LOG_SERVER_URL);
    }


    private static Map<Class, CollectLogParam> dbParameterListCacheMap = new ConcurrentHashMap<>();

    /**
     * 添加入库数据
     */
    public static void addMysqlLog(Class clz, String saveSql, List<Map<Integer, DbParameter>> dbParameterList) {
        synchronized (dbParameterListCacheMap) {
            CollectLogParam collectLogParam = dbParameterListCacheMap.get(clz);
            if (collectLogParam == null) {
                collectLogParam = new CollectLogParam(clz, saveSql, dbParameterList);
                dbParameterListCacheMap.put(clz, collectLogParam);
            } else {
                collectLogParam.getDbParameterList().addAll(dbParameterList);
            }
            countNum += dbParameterList.size();
        }
    }

    private static Map<Class, CollectLogParam> ckParameterListCacheMap = new ConcurrentHashMap<>();

    private static void addCKLog(Class clz, String saveSql, List<Map<Integer, DbParameter>> dbParameterList) {
        if (!isClickHouseOpen()) {
            // 没有配置clickhouse日志服务器地址
            addMysqlLog(clz, saveSql, dbParameterList);
            logger.error("addCKLog error, clickhouse server off");
            return;
        }
        // 日志太多了，超过的先入数据库
        if (ckCountNum > 100000) {
            addMysqlLog(clz, saveSql, dbParameterList);
            logger.error("addCKLog error, ckCountNum limit");
            return;
        }
        synchronized (ckParameterListCacheMap) {
            CollectLogParam collectLogParam = ckParameterListCacheMap.get(clz);
            if (collectLogParam == null) {
                collectLogParam = new CollectLogParam(clz, saveSql, dbParameterList);
                ckParameterListCacheMap.put(clz, collectLogParam);
            } else {
                collectLogParam.getDbParameterList().addAll(dbParameterList);
            }
            ckCountNum += dbParameterList.size();
        }
        // ckCountNum限制
        if (ckCountNum > 10000) {
            sendCKLog();
        }
    }


    public static void saveMysql() {
        long startTime = DateHelper.getCurrentTime();
        int needSaveCountNum = countNum;
        int saveCountNum = 0;
        Map<Class, CollectLogParam> tempDbParameterListCacheMap = new ConcurrentHashMap<>();
        synchronized (dbParameterListCacheMap) {
            tempDbParameterListCacheMap = new ConcurrentHashMap<>(dbParameterListCacheMap);
            dbParameterListCacheMap.clear();
            countNum = 0;
        }
        DBManager db = DatabaseMgr.getDbManager();
        db.setConnName(DbNameStrategy.getLogDb());
        for (Map.Entry<Class, CollectLogParam> entry : tempDbParameterListCacheMap.entrySet()) {
            long saveStartTime = DateHelper.getCurrentTime();
            Class clz = entry.getKey();
            if (db.executeBatchQuery(entry.getValue().getSql(), entry.getValue().getDbParameterList()) > -1) {
                saveCountNum += entry.getValue().getDbParameterList().size();
            } else {
                // TODO 加个容错 把丢失的日志写入到文件里去
                logger.error("保存日志错误 className : {}, sql :{}", entry.getKey().getName(), entry.getValue().getSql());
            }
            long saveUseTime = DateHelper.getCurrentTime() - saveStartTime;
            if (saveUseTime > 3000) {
                logger.warn("保存日志错误超时.时间为：{}ms,数据个数：{}， className = {}", saveUseTime, entry.getValue().getDbParameterList().size(), clz.getName());
            }
        }
        long allUseTime = DateHelper.getCurrentTime() - startTime;
        logger.info("AutoLogMgr save 统计.时间为：{}ms,需要保存数据个数：{}，实际保存数据个数：{} ", allUseTime, needSaveCountNum, saveCountNum);
    }

    public static void stop() {
        logger.info("AutoLogMgr stop start");
        save();
        saveMysql();
        sendCKLog();
        logger.info("AutoLogMgr stop end");
    }


    public static void main(String[] args) throws Exception {

        BigInteger ii = BigInteger.valueOf(1);
        System.out.println(JSONArray.toJSONString(ii));
        System.out.println(JSONArray.toJSONString("112312313"));
        // 测试反射性能
        ConfigHelper.initConfig(".\\Config\\config.properties");
        DBManager.initConfig(ConfigHelper.getValue("db.path"));
        DBManager.initGameDbConfig(2);

        DBTableInfoMgr dbTableInfoMgr = new DBTableInfoMgr();
        dbTableInfoMgr.init(DbNameStrategy.getLogDb(), "com.yanqu.road.entity.log", true);

        GameServer.getInstance().setDbTableInfoMgr(dbTableInfoMgr);
        LogShuShanBuildUp buildUpData = new LogShuShanBuildUp(1, 1, 1, 1, 1, 1, "", 1);

        LogAutoTestObject autoTestObjectData = new LogAutoTestObject((byte) 1, (short) 1, 1, 1, false, "test", new ArrayList(), new HashMap(), BigInteger.valueOf(2), new Property(), new Date(), BigDecimal.valueOf(1));

        for (int i = 0; i < 10000; i++) {
            add(buildUpData);
            add(autoTestObjectData);
        }
        long startTime = DateHelper.getCurrentTime();

        save();
        long endTime = DateHelper.getCurrentTime();

        System.out.println("耗时：" + (endTime - startTime));
//        Class clz = LogShuShanBuildUp.class;
//        TableInfo tableInfo = dbTableInfoMgr.getTableInfoByClass(clz);
//        long startTime = DateHelper.getCurrentTime();
//        LogShuShanBuildUp data = new LogShuShanBuildUp(1,1,1,1,1,1,"",1);
//        Map<Integer, DbParameter> para = new HashMap<>();
//        int i = 1;
//        for (int z = 0; z < 100000; z++) {
//            for (int j = 0; j < tableInfo.getFieldTypes().size(); j++) {
//                Field field = clz.getDeclaredField(tableInfo.getFieldNames().get(j));
//                field.setAccessible(true);
//                para.put(i++, new DbParameter(tableInfo.getFieldTypes().get(j), field.get(data)));
//            }
//        }
//        long endTime = DateHelper.getCurrentTime();
//
//        System.out.println("耗时："+(endTime - startTime));

    }


    /**
     * 发送 日志对象
     */
    public static void sendCKLog() {
        try {
            if (ckParameterListCacheMap.isEmpty()) {
                return;
            }
            if (!isClickHouseOpen()) {
                synchronized (ckParameterListCacheMap) {
                    // ck服务关了，这里还有没发送的写入mysql
                    for (Map.Entry<Class, CollectLogParam> entry : ckParameterListCacheMap.entrySet()) {
                        addMysqlLog(entry.getKey(), entry.getValue().getSql(), entry.getValue().getDbParameterList());
                    }
                    ckParameterListCacheMap.clear();
                }
                return;
            }
            synchronized (ckParameterListCacheMap) {
                List<CkLogParam> ckLogParamList = new ArrayList<>();
                for (CollectLogParam logParam : ckParameterListCacheMap.values()) {
                    CkLogParam ckLogParam = new CkLogParam();
                    ckLogParam.setClassName(logParam.getCls().getName());
                    List<Map<Integer, CkDbData>> dataList = new ArrayList<>();
                    for (Map<Integer, DbParameter> parameterMap : logParam.getDbParameterList()) {
                        Map<Integer, CkDbData> ckDbDataMap = new HashMap<>();
                        for (Map.Entry<Integer, DbParameter> entry : parameterMap.entrySet()) {
                            ckDbDataMap.put(entry.getKey(), new CkDbData(entry.getValue().getDbType(), entry.getValue().getResult()));
                        }
                        dataList.add(ckDbDataMap);
                    }
                    ckLogParam.setDataList(dataList);
                    ckLogParam.setSql(logParam.getSql());
                    ckLogParamList.add(ckLogParam);
                }
                // 发送到日志收集服务
                String response = HttpHelper.doPostJson(ChannelConfig.CLICKHOUSE_LOG_SERVER_URL, JSON.toJSONString(ckLogParamList), "UTF-8");
                if (response.equals("success")) {
                    ckParameterListCacheMap.clear();
                    ckCountNum = 0;
                } else {
                    logger.error("sendCKLog error response:{}", response);
                }

            }
        } catch (Throwable e) {
            logger.error("AutoLogMgr sendLog error", e);
        }
    }
}
